package com.gralves.elasticsearch.utils.indexes;

import com.gralves.elasticsearch.utils.anno.Array;
import com.gralves.elasticsearch.utils.anno.EntityProperty;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.ResourceAlreadyExistsException;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author zhouyibin
 * @date 2018/12/17
 * @desc 创建索引工具类
 */
@Slf4j
@Component
public class IndexFactory {
    @Value("${elasticsearch.numberOfShard}")
    private Integer shard;
    @Value("${elasticsearch.numberOfReplicas}")
    private Integer replicas;

    private final TransportClient transportClient;

    @Autowired
    public IndexFactory(TransportClient transportClient) {
        this.transportClient = transportClient;
    }


    public <T> String createIndex(Class clazz, String indexName) {
        long start =System.currentTimeMillis();
        log.info(">>>>>>>>>>>>>>>>>>>>>>>> [ 开始创建索引 time : {} ] <<<<<<<<<<<<<<<<<<<<<<<<", new SimpleDateFormat("yyyy-MM-dd HH-mm-ss").format(new Date()));
        /*索引名称必须小写*/
        indexName = indexName.toLowerCase();
        log.info(">>>>>>>>>>>>>>>>>>>>>>>> [ 索引名称 : {} ] <<<<<<<<<<<<<<<<<<<<<<<<",indexName);
        /*获取类对象对象*/
//        Class<?> clazz = entity.getClass();
        /*获取所有类字段*/
        Field[] fields = clazz.getDeclaredFields();
        log.info(">>>>>>>>>>>>>>>>>>>>>>>> [ 分片数量 : {} , 副本数量 : {} ] <<<<<<<<<<<<<<<<<<<<<<<<",shard,replicas);
        /*设置分片数量*/
        Settings settings = Settings.builder()
                /*默认3个主分片*/
                .put("number_of_shards", shard)
                /*默认0个复制分片==>单机版配置;集群副本数量最多不超过2==>副本越多,性能越低*/
                .put("number_of_replicas", replicas).build();
        try {
            /*索引名称不为空则创建新的索引*/
            if (indexName != null && indexName.length() > 0) {
                log.info("[新建索引]  >>>>>>>>>  {}", indexName);
                transportClient
                        .admin()
                        .indices()
                        .prepareCreate(indexName)
                        .setSettings(settings)
                        .execute()
                        .actionGet();
            }
        } catch (ResourceAlreadyExistsException e) {
            log.error("[创建索引失败]  >>>>>>>>>  {}", e.getMessage());
            /*删除索引*/
            log.error("[删除当前索引]  >>>>>>>>>  {}", indexName);
            transportClient
                    .admin()
                    .indices()
                    .delete(new DeleteIndexRequest(indexName))
                    .actionGet();
            /*重建索引*/
            log.info("[索引重建]  >>>>>>>>>  {}", indexName);
            transportClient
                    .admin()
                    .indices()
                    .prepareCreate(indexName)
                    .setSettings(settings)
                    .execute()
                    .actionGet();
        }
        XContentBuilder builder = null;
        try {
            builder = XContentFactory.jsonBuilder();
            builder.startObject()
                    .startObject(indexName)
                    .startObject("_all")
                    /*关闭_all字段*/
                    .field("enabled", false)
                    .endObject()
                    .startObject("_source")
                    .field("enabled", true)
                    .endObject()
                    /*properties 为包含文档所有的属性字段*/
                    .startObject("properties");
            /*映射字段转换*/
            combineClassInfo(builder, fields);
            builder.endObject();
            builder.endObject();
            builder.endObject();
        } catch (IOException | ClassNotFoundException e) {
            log.error("[文档结构创建失败] >>>>>>>>>>>>>>>>>>  {}", e.getMessage());
            throw new RuntimeException("文档结构创建失败");
        }
        transportClient
                .admin()
                .indices()
                .putMapping(Requests.putMappingRequest(indexName)
                        .type(indexName)
                        .source(builder)
                ).actionGet();
        long end =System.currentTimeMillis();
        log.info(">>>>>>>>>>>>>>>>>>>>>>>> [ 索引创建完成 time : {} ] <<<<<<<<<<<<<<<<<<<<<<<<", new SimpleDateFormat("yyyy-MM-dd HH-mm-ss").format(new Date()));
        log.info(">>>>>>>>>>>>>>>>>>>>>>>> [ 总耗时 : {} ms ] <<<<<<<<<<<<<<<<<<<<<<<<", end-start);
        return Strings.toString(builder.prettyPrint());
    }

    private void combineClassInfo(XContentBuilder builder, Field[] fields) throws IOException, ClassNotFoundException {
        for (Field field : fields) {
            switchType(builder, field);
        }
    }

    private void switchType(XContentBuilder builder, Field field) throws IOException {
        String name = field.getName();
        builder.startObject(name);
        String typeName = field.getGenericType().getTypeName();
        String classPath = null;
        if (field.isAnnotationPresent(EntityProperty.class)) {
            classPath = field.getAnnotation(EntityProperty.class).value();
            typeName = "pojo";
        }
        if (field.isAnnotationPresent(Array.class)) typeName = "array";
        try {
            switch (typeName) {
                case "int":
                case "long":
                case "java.lang.Integer":
                case "java.lang.Long":
                    builder.field("store", true);
                    builder.field("type", "long").endObject();
                    break;
                case "java.lang.String":
                    builder.field("store", true);
                    builder.field("type", "keyword").endObject();
                    break;
                case "java.sql.Date":
                case "java.util.Date":
                    builder.field("store", true);
                    builder.field("type", "date")
                            .field("format", "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd || epoch_millis")
                            .endObject();
                    break;
                case "boolean":
                case "java.lang.Boolean":
                    builder.field("store", true);
                    builder.field("type", "boolean").endObject();
                    break;
                case "java.math.BigDecimal":
                    builder.field("store", true);
                    builder.field("type", "double").endObject();
                    break;
                case "pojo":/*自定义Java对象*/
                    builder.startObject("properties");
                    Class<?> clazz = Class.forName(classPath);
                    Field[] fields = clazz.getDeclaredFields();
                    for (Field f : fields) this.switchType(builder, f);
                    builder.endObject().field("type", "object").endObject();
                    break;
                default:
                    builder.field("store", true);
                    builder.field("type", "text").endObject();
            }
        } catch (Exception e) {
            log.error("[类型异常]");
        }
    }
}
